package com.kingwang.demo.springsecuritytokendemo.security;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 基于Token的认证过滤器
 *
 * @author King
 */
public class TokenBasedAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public TokenBasedAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
        super(requiresAuthenticationRequestMatcher);
    }

    /**
     * 准备进行认证
     * 如果存在多种认证方式，需要在这里创建不同的Authentication对象再
     * 调用AuthenticationManager.authenticate(Authentication)
     * <p>
     * 如果是最后一个Filter且无法正常生成认证凭据，应该抛出异常
     * <p>
     * 如果认证规则需要进行多步，例如先跳转到另外一个URL再继续，应在attemptAuthentication方法中进行跳转，然后返回null
     * <p>
     * 如果需要在同一个HttpSecurity配置中同时支持多种登录认证方式，需要返回不同的Authentication实现类，
     * 以实现使用不同的AuthenticationProvider处理不同的登录认证localhost:8080方式
     *
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

        Authentication existAuthentication = SecurityContextHolder.getContext().getAuthentication();

        if (existAuthentication != null && existAuthentication.isAuthenticated()) {
            return existAuthentication;
        }

        Authentication authentication;

        String tokenParam = getToken(request);

        if (StringUtils.isBlank(tokenParam)) {
            throw new BadCredentialsException("Token认证失败：无法找到Token");
        }

        String[] tokenParts = tokenParam.split(" ");
        if (tokenParts.length <= 1) {
            throw new BadCredentialsException("Token认证失败：非法的Token：" + tokenParam);
        }

        String tokenType = tokenParts[0];
        String token = tokenParts[1];

        authentication = new TokenBasedAuthenticationToken(tokenType, token);

        return this.getAuthenticationManager().authenticate(authentication);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                    + authResult);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);

        // Fire event
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }

        // 这里不能再跳转，只能继续执行Filter，否则会出现循环重复认证的情况
        chain.doFilter(request, response);
    }

    private String getToken(HttpServletRequest request) {
        String ticket = request.getHeader(HttpHeaders.AUTHORIZATION);

        return ticket;
    }
}
